package state

import (
	"math/big"
	"sort"

	logger "hundsun.com/hsl/hschain/common/log"
	"hundsun.com/hsl/hschain/contract/evm/ext/common"
	pbcom "hundsun.com/hsl/hschain/protos/common"
	pbevm "hundsun.com/hsl/hschain/protos/evm"
	"hundsun.com/hsl/hschain/store/extend/account"
)

// EVMStateObject 合约账户对象
type EVMStateObject struct {
	// 状态数据句柄
	sdb *EVMStateDB

	// 合约账户余额
	acc *pbcom.Account
	// 合约信息
	info *pbevm.EVMContractInfo
	// 合约更新
	isUpdate bool

	// TODO 合约ContractIdentifier后续在考虑进来
	// 是否只读(用于变量读写标记)
	readOnly bool
	// 标记变量读写状况
	rwSign map[string]bool // true:write  false:read
	// 账户地址
	addr common.Address
	// 合约code hash
	codeHash common.Hash

	// 生成的状态数据缓存
	genStateCache map[string]common.Hash
}

// NewEVMStateObject 创建一个合约对象
func NewEVMStateObject(addr common.Address, sdb *EVMStateDB, readOnly bool) *EVMStateObject {
	if len(addr) == 0 || sdb == nil {
		logger.Errorf("NewEVMStateObject param miss error contract addr %x statedb is %v nil", addr, sdb == nil)
		return nil
	}
	eso := &EVMStateObject{
		sdb:           sdb,
		addr:          addr,
		readOnly:      readOnly,
		rwSign:        make(map[string]bool),
		genStateCache: make(map[string]common.Hash),
		isUpdate:      true, // 将新建账户存入临时缓存中, 需要最后addTempCache
	}
	addrstr := common.Bytes2Hex(addr.Bytes())
	eso.acc = &pbcom.Account{
		InternalAddress: addrstr,
		Type:            pbcom.Account_USER, // 首先设置为USER类型账户，通过设置code修改为合约账户
	}

	eso.info = &pbevm.EVMContractInfo{}

	// 标记变量读写状态
	eso.UpdateReadWriteSign(genEvmContractInfoKey(addr.Bytes()), true)
	eso.UpdateReadWriteSign([]byte(addrstr), true)

	sdb.addChange(createAccountChange{
		account: addr,
	})
	return eso
}

// LoadEVMStateObject 载入一个合约对象
func LoadEVMStateObject(addr common.Address, sdb *EVMStateDB, readOnly bool) *EVMStateObject {
	if len(addr) == 0 || sdb == nil {
		logger.Errorf("LoadEVMStateObject param miss error contract addr %x statedb is %v nil", addr, sdb == nil)
		return nil
	}
	accdb := account.NewAccountDB(sdb.execCtx)
	addrstr := common.Bytes2Hex(addr.Bytes())
	acc, err := accdb.GetAccount(addrstr)
	if acc == nil || err != nil { // 不存在则返回
		return nil
	}
	eso := &EVMStateObject{
		sdb:           sdb,
		acc:           acc,
		addr:          addr,
		readOnly:      readOnly,
		rwSign:        make(map[string]bool),
		genStateCache: make(map[string]common.Hash),
	}
	evmdb := NewEVMContractInfoDB(sdb.execCtx)
	info, _ := evmdb.GetEVMContractInfo(addr.Bytes())
	if info != nil {
		eso.codeHash = common.CalcHash(info.GetCode())
		eso.info = info
		eso.UpdateReadWriteSign(genEvmContractInfoKey(addr.Bytes()), false)
	} else {
		// 重新生成新的info
		eso.info = &pbevm.EVMContractInfo{}
		eso.isUpdate = true
		eso.UpdateReadWriteSign(genEvmContractInfoKey(addr.Bytes()), true)
	}
	// 标记变量读写状态
	eso.UpdateReadWriteSign([]byte(addrstr), false)
	return eso
}

// SubBalance sub balance
func (so *EVMStateObject) SubBalance(amount *big.Int) {
	so.UpdateReadWriteSign([]byte(so.acc.InternalAddress), true)
	so.sdb.addChange(balanceChange{
		account: so.addr,
		prev:    so.acc.Balance,
	})
	if so.acc.Balance < amount.Uint64() {
		so.acc.Balance = 0
	} else {
		so.acc.Balance -= amount.Uint64()
	}
	so.isUpdate = true
}

// AddBalance add balance
func (so *EVMStateObject) AddBalance(amount *big.Int) {
	so.UpdateReadWriteSign([]byte(so.acc.InternalAddress), true)
	so.sdb.addChange(balanceChange{
		account: so.addr,
		prev:    so.acc.Balance,
	})
	so.acc.Balance += amount.Uint64()
	so.isUpdate = true
}

// GetBalance 获取balance值
func (so *EVMStateObject) GetBalance() *big.Int {
	so.UpdateReadWriteSign([]byte(so.acc.InternalAddress), false)
	return big.NewInt(int64(so.acc.Balance))
}

// GetState 获取状态数据 先从当前的缓存中获取，如果获取不到，再从状态db中获取
func (so *EVMStateObject) GetState(key common.Hash) common.Hash {
	stateKey := genEvmStateDataKey(so.addr.Bytes(), key.Bytes())
	// 更新读写变量状态
	so.UpdateReadWriteSign(stateKey, false)
	if val, ok := so.genStateCache[string(stateKey)]; ok {
		return val
	}
	val, err := so.sdb.execCtx.Get(stateKey)
	if len(val) == 0 || err != nil {
		return common.Hash{}
	}
	return common.BytesToHash(val)
}

// SetState 设置状态数据
func (so *EVMStateObject) SetState(key, value common.Hash) {
	stateKey := genEvmStateDataKey(so.addr.Bytes(), key.Bytes())
	so.UpdateReadWriteSign(stateKey, true)
	so.sdb.addChange(storageChange{
		account:  so.addr,
		key:      key,
		prevalue: so.GetState(key),
	})
	// 设置到本地
	so.genStateCache[string(stateKey)] = value
}

// SetCode 设置合约二进制代码,同步生成代码哈希且检查账户类型
func (so *EVMStateObject) SetCode(code []byte) {
	so.UpdateReadWriteSign(genEvmContractInfoKey(so.addr.Bytes()), true)
	if so.info == nil {
		so.info = &pbevm.EVMContractInfo{
			Code:     code,
			Nonce:    0,
			Suicided: false,
			Abi:      "",
		}
	}
	so.sdb.addChange(codeChange{
		account:  so.addr,
		prevcode: so.GetCode(),
		prevhash: so.codeHash,
		preType:  so.acc.Type,
	})
	so.info.Code = code
	// 设置code则说明该新建账户为合约账户
	if so.acc.Type != pbcom.Account_CONTRACT {
		so.acc.Type = pbcom.Account_CONTRACT
	}
	so.codeHash = common.CalcHash(code)
	so.isUpdate = true
}

// GetCode 获取合约code
func (so *EVMStateObject) GetCode() []byte {
	so.UpdateReadWriteSign(genEvmContractInfoKey(so.addr.Bytes()), true)
	return so.info.GetCode()
}

// SetAbi 设置合约绑定的ABI数据
func (so *EVMStateObject) SetAbi(abi string) {
	so.UpdateReadWriteSign(genEvmContractInfoKey(so.addr.Bytes()), true)
	so.sdb.addChange(abiChange{
		account: so.addr,
		prevabi: so.GetAbi(),
	})
	so.info.Abi = abi
	so.isUpdate = true
}

// GetAbi 获取合约绑定的ABI数据
func (so *EVMStateObject) GetAbi() string {
	so.UpdateReadWriteSign(genEvmContractInfoKey(so.addr.Bytes()), false)
	return so.info.GetAbi()
}

// GetReadWriteSign 获取对当前账户数据的读取
func (so *EVMStateObject) GetReadWriteSign() map[string]bool {
	return so.rwSign
}

// GetContractKV 合约扩展的一些变化
func (so *EVMStateObject) GetContractKV() (kvSet []*pbcom.KeyValue) {
	if so.isUpdate { // 修改的account, ContractInfo状态数据
		// acc的变化
		acckv, _ := account.KV(so.acc)
		kvSet = append(kvSet, acckv)
		// info的变化
		infokv, _ := EVMContractInfoDBKV(so.info, so.addr.Bytes())
		kvSet = append(kvSet, infokv)
	}
	for k, v := range so.genStateCache {
		value := make([]byte, len(v))
		copy(value, v[:])
		kvSet = append(kvSet, &pbcom.KeyValue{Key: []byte(k), Value: value})
	}
	if len(kvSet) > 0 {
		sort.Slice(kvSet, func(i, j int) bool {
			return string(kvSet[i].Key) < string(kvSet[j].Key) // 进行排序
		})
	}
	return
}

// Suicide 自杀
func (so *EVMStateObject) Suicide() bool {
	so.UpdateReadWriteSign(genEvmContractInfoKey(so.addr.Bytes()), true)
	so.sdb.addChange(suicideChange{
		account: so.addr,
		prev:    so.HasSuicided(),
	})
	so.info.Suicided = true
	so.isUpdate = true
	return true
}

// HasSuicided 是否已经自杀
func (so *EVMStateObject) HasSuicided() bool {
	so.UpdateReadWriteSign(genEvmContractInfoKey(so.addr.Bytes()), false)
	return so.info.GetSuicided()
}

// Empty 是否为空对象
func (so *EVMStateObject) Empty() bool {
	so.UpdateReadWriteSign(genEvmContractInfoKey(so.addr.Bytes()), false)
	// TODO 后续决定是否加入对info非空的判断
	// so.acc == nil || (so.acc.GetBalance() == 0 && so.acc.GetFrozen() == 0 && (so.info == nil || len(so.info.Code) == 0))
	return so.acc == nil
}

// SetNonce 设置nonce值
func (so *EVMStateObject) SetNonce(nonce uint64) {
	so.UpdateReadWriteSign(genEvmContractInfoKey(so.addr.Bytes()), true)
	so.sdb.addChange(nonceChange{
		account: so.addr,
		prev:    so.GetNonce(),
	})
	so.info.Nonce = nonce
}

// GetNonce 获取nonce值
func (so *EVMStateObject) GetNonce() uint64 {
	so.UpdateReadWriteSign(genEvmContractInfoKey(so.addr.Bytes()), false)
	return so.info.GetNonce()
}

// UpdateReadWriteSign 更新读写标记
func (so *EVMStateObject) UpdateReadWriteSign(key []byte, sign bool) {
	if so.readOnly {
		return
	}
	if sign, ok := so.rwSign[string(key)]; ok && sign { // 如果已经做了标记且标记为写则不做更新
		return
	}
	so.rwSign[string(key)] = sign
}
